package forum.server.persistence.old;

import forum.server.controller.MyLogger;
import forum.server.controller.Security;
import forum.server.domain.*;
import forum.server.exceptions.*;
import forum.server.persistence.DataManipulatorInt;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class DataManipulator implements DataManipulatorInt {
    private JAXBContext jc;
    private Unmarshaller u;
    private Marshaller m;
    private static DataManipulator instance = null;
    private int lastId;
    private static MyLogger logger;
    
    /**
     * private constructor for the singleton pattern.
     * @throws DatabaseException
     */
    private DataManipulator() throws DatabaseException {
        try{
            this.jc = JAXBContext.newInstance("forum.server.persistence");
            this.u = jc.createUnmarshaller();
            this.m = jc.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            lastId=0;
            DataManipulator.logger=MyLogger.getInstance();
        }
        catch (JAXBException e) {
        	DataManipulator.logger.logSevere("Data Base: JAXB failure.");
        	throw new DatabaseException("problem with JAXB mechanism.");}
    }
    
    /**
     * singleton pattern.
     * @return the datamanipulator is returned.
     */
    public static DataManipulator getInstance() throws DatabaseException {
        if (DataManipulator.instance == null){
        	DataManipulator.instance=new DataManipulator();
        	return instance;
        }
        else
            return instance;
    }
    /**
     * This function returns all the posts from the file.
     */
    private List<PostTableEntry> getFilePosts() throws DatabaseException{
        FileInputStream in = null;
        try {
            in = new FileInputStream("Posts.xml");
            // Obtain the data from the XML file.
            PostRoot root = (PostRoot)this.u.unmarshal(in);
            in.close();
            List<PostTableEntry> posts=root.getPosts();
            return posts;
        }
        catch (JAXBException e) {
            DataManipulator.logger.logSevere("Data Base: JAXB failure.");
        	throw new DatabaseException("JAXB problem while reading posts file");
        }
        catch (FileNotFoundException e) {
        	DataManipulator.logger.logSevere("Data Base: posts file not found.");
            throw new DatabaseException("posts file not found.");
        }
        catch (IOException e) {
        	DataManipulator.logger.logSevere("Data Base: IO failure.");
            throw new DatabaseException("IO problem reading Posts file.");
        }
        //return new ArrayList<PostTableEntry>();
    }

    /**
     * This function returns all the users from the file.
     */
    private List<UserTableEntry> getFileUsers() throws DatabaseException{
        FileInputStream in = null;
        try {
                in = new FileInputStream("Users.xml");
                // Obtain the data from the XML file.
                UserRoot root = (UserRoot)this.u.unmarshal(in);
                in.close();
                List<UserTableEntry> users=root.getUsers();
                return users;
        }
        catch (JAXBException e) {
        	DataManipulator.logger.logSevere("Data Base: JAXB failure.");
            throw new DatabaseException("JAXB problem while reading users file");
        }
        catch (FileNotFoundException e) {
        	DataManipulator.logger.logSevere("Data Base: users file not found.");
            throw new DatabaseException("users file not found.");
        }
        catch (IOException e) {
        	DataManipulator.logger.logSevere("Data Base: IO failure.");
            throw new DatabaseException("IO problem reading users file.");
        }
        //return new ArrayList<UserTableEntry>();
    }

    /**
     * updates the posts file.
     */
    private void setFilePosts(List<PostTableEntry> posts) throws DatabaseException {
        try{
            FileOutputStream out = new FileOutputStream("Posts.xml");
            PostRoot root=new PostRoot();
            root.setPosts(posts);
            this.m.marshal(root,out);
            out.close();
        }
        catch (JAXBException e) {
        	DataManipulator.logger.logSevere("Data Base: JAXB failure.");
            throw new DatabaseException("JAXB problem while writing to posts file");
        }
        catch (FileNotFoundException e) {
        	DataManipulator.logger.logSevere("Data Base: posts file not found.");
            throw new DatabaseException("posts file not found.");
        }
        catch (IOException e) {
        	DataManipulator.logger.logSevere("Data Base: IO failure.");
            throw new DatabaseException("IO problem writing to posts file.");
        }
    }

    /**
     * updates the users file.
     */
    private void setFileUsers(List<UserTableEntry> users) throws DatabaseException {
        try{
            FileOutputStream out = new FileOutputStream("Users.xml");
            UserRoot root=new UserRoot();
            root.setUsers(users);
            this.m.marshal(root,out);
            out.close();
        }
        catch (JAXBException e) {
        	DataManipulator.logger.logSevere("Data Base: JAXB failure.");
            throw new DatabaseException("JAXB problem while writing to users file");
        }
        catch (FileNotFoundException e) {
        	DataManipulator.logger.logSevere("Data Base: users file not found.");
            throw new DatabaseException("users file not found.");
        }
        catch (IOException e) {
        	DataManipulator.logger.logSevere("Data Base: IO failure.");
            throw new DatabaseException("IO problem writing to posts file.");
        }
    }
    /**
     * This function resets the db.
     */
    public void resetDataBase() throws DatabaseException {
        setFilePosts(new ArrayList<PostTableEntry>());
        setFileUsers(new ArrayList<UserTableEntry>());
        this.lastId=0;
        try {
        	addUser(new Admin("admin", Security.encryptPassword("1234")));
		} catch (NoSuchAlgorithmException e) {
			logger.logException("Password encryption error in DM.", e);
			System.exit(1);
		}
    }
    /**
     * Returns the max postId in the db +1.
     */
    @Override
    public int generateNewPostId() throws DatabaseException{
        if(this.lastId>0){
            this.lastId++;
            return this.lastId;
        }
        List<PostTableEntry> posts=getFilePosts();
        if(posts.size()==0){
            this.lastId++;
            return this.lastId;
        }
        int maxId=-1;
        for(int i=0;i<posts.size();i++){
            int id=posts.get(i).getPostId();
            if(id>maxId)
                maxId=id;
        }
        this.lastId=maxId+1;
        return this.lastId;
    }

    @Override
    public Vector<Post> getReplies(int postId) throws DatabaseException {
        Vector<Post> result=new Vector<Post>();
        List<PostTableEntry> posts=getFilePosts();
        for(int i=0;i<posts.size();i++){
            PostTableEntry tPost=posts.get(i);
            if(tPost.getParentId()==postId)
                result.add(PostTableEntry.tableEntryToPost(tPost));
        }
        return result;
    }

    @Override
    public Vector<Post> getPosts() throws DatabaseException {
        Vector<Post> result=new Vector<Post>();
        List<PostTableEntry> posts=getFilePosts();
        for(int i=0;i<posts.size();i++){
            PostTableEntry tPost=posts.get(i);
            if(tPost.getParentId()==-1)
                result.add(PostTableEntry.tableEntryToPost(tPost));
        }
        return result;
    }
    
    @Override
    public void deletePost(int postId)throws DatabaseException {
        List<PostTableEntry> posts=getFilePosts();
        boolean found = false;
        for(int i=0;i<posts.size();i++){
            PostTableEntry tPost=posts.get(i);
            if(tPost.getPostId()==postId || tPost.getParentId()==postId) {
                posts.remove(tPost);
                found = true;
            }
        }
        if (!found) {
        	DataManipulator.logger.logInfo("Data Base: Post not found.");
            throw new DatabaseException("Data Base: Post not in the Data Base.");
        }
        setFilePosts(posts);
    }

    @Override
    public void deleteUser(String username)
            throws DatabaseException, NoSuchUserException {
        List<UserTableEntry> users=getFileUsers();
        boolean found=false;
        for(int i=0;i<users.size();i++){
            UserTableEntry tUser=users.get(i);
            if(tUser.getUsername().equals(username)){
                users.remove(tUser);
                found=true;
            }
        }
        if(!found){
        	DataManipulator.logger.logInfo("Data Base: User not found.");
            throw new NoSuchUserException("User does not exists in the Data Base.");
        }
        setFileUsers(users);
    }

    @Override
    public Post getPost(int postId)
    throws DatabaseException, NoSuchPostException {
        List<PostTableEntry> posts=getFilePosts();
        for(int i=0;i<posts.size();i++){
            PostTableEntry tPost=posts.get(i);
            if(tPost.getPostId()==postId)
                return PostTableEntry.tableEntryToPost(tPost);
        }
        DataManipulator.logger.logInfo("Data Base: Post not found.");
        throw new NoSuchPostException("Parent post doesn't exist.");
    }
    @Override
    public Vector<LoggedInUser> getAllUsers() throws DatabaseException{
    	Vector<LoggedInUser> result=new Vector<LoggedInUser>();
    	List<UserTableEntry> users=getFileUsers();
        for(int i=0;i<users.size();i++){
        	result.add(UserTableEntry.tableEntryToUser(users.get(i)));      
        }
        return result;
    }
    @Override
    public LoggedInUser getUser(String username)
    throws DatabaseException, NoSuchUserException {
        List<UserTableEntry> users=getFileUsers();
        for(int i=0;i<users.size();i++){
            UserTableEntry tUser=users.get(i);
            if(tUser.getUsername().equals(username))
                return UserTableEntry.tableEntryToUser(tUser);
        }
        DataManipulator.logger.logInfo("Data Base: User not found.");
        throw new NoSuchUserException("Invalid username.");
    }


    @Override
    public void addUser(LoggedInUser user) throws DatabaseException{
        List<UserTableEntry> users=getFileUsers();
        if(isUserExists(user.getUsername())){
        	throw new DatabaseException("Username already exists, choose another");
        }
        users.add(new UserTableEntry(user));
        setFileUsers(users);
    }


    @Override
    public boolean isUserExists(String user) throws DatabaseException {
        List<UserTableEntry> users=getFileUsers();
        for(int i=0;i<users.size();i++){
            UserTableEntry tRegUser=(UserTableEntry)users.get(i);
            if(tRegUser.getUsername().equals(user))
                return true;
        }
        return false;
    }

    /**
     * returns the number of users registered in the db.
     * does not include guests, includes moderators and admins.
     */
    @Override
    public int countUsers() throws DatabaseException {
        return getFileUsers().size();
    }

    /**
     * @param fatherId the id of the parent to the reply.
     * @param reply the reply to add to the post.
     * adds a reply to the db.
     */
    @Override
    public int addReply(int fatherId, Post reply) throws DatabaseException {
        List<PostTableEntry> posts=getFilePosts();
        reply.setParentId(fatherId);
        posts.add(new PostTableEntry(reply));
        setFilePosts(posts);
        return reply.getPostId();
    }


    @Override
    public int addPost(Post p) throws DatabaseException {
        return addReply(-1,p);
    }

    @Override
    public void modifyPost(int postId, PostContent pc) 
    throws DatabaseException {
        List<PostTableEntry> posts=getFilePosts();
        boolean found=false;
        for(int i=0;i<posts.size();i++){
            PostTableEntry tPost=posts.get(i);
            if(tPost.getPostId()==postId){
                tPost.setContent(pc);
                found=true;
                break;
            }
        }
        if(!found) {
        	throw new DatabaseException("Post not found");
        }
        setFilePosts(posts);
    }
    
    @Override
    public LoggedInUser demoteModerator(String username)
    throws DatabaseException, NoSuchUserException {
        return changeUserType(username, UserType.LOGGEDINUSER);
    }
    
    @Override
    public Moderator promoteModerator(String username)
    throws DatabaseException, NoSuchUserException {
        return (Moderator)changeUserType(username, UserType.MODERATOR);
    }
    
    private LoggedInUser changeUserType(String username,UserType newType)
    throws DatabaseException, NoSuchUserException {
        List<UserTableEntry> users=getFileUsers();
        for(int i=0;i<users.size();i++){
            UserTableEntry tUser=users.get(i);
            if(tUser.getUsername().equals(username)){
                tUser.setType(newType);
                setFileUsers(users);
                return UserTableEntry.tableEntryToUser(tUser);
            }
        }
        DataManipulator.logger.logInfo("Data Base: User not found.");
        throw new NoSuchUserException("no such username exists.");
    }

	@Override
	public Vector<Moderator> getAllModerators() throws DatabaseException {
		Vector<Moderator> result=new Vector<Moderator>();
    	List<UserTableEntry> users=getFileUsers();
        for(int i=0;i<users.size();i++){
        	UserTableEntry tUser=users.get(i);
        	if(tUser.getType()==UserType.MODERATOR)
        		result.add((Moderator)UserTableEntry.tableEntryToUser(tUser));      
        }
        return result;
	}

	@Override
	public Vector<LoggedInUser> getAllUsersForPromotion()
			throws DatabaseException {
		Vector<LoggedInUser> result=new Vector<LoggedInUser>();
    	List<UserTableEntry> users=getFileUsers();
        for(int i=0;i<users.size();i++){
        	UserTableEntry tUser=users.get(i);
        	if(tUser.getType()!=UserType.MODERATOR && tUser.getType()!=UserType.ADMIN)
        		result.add(UserTableEntry.tableEntryToUser(tUser));      
        }
        return result;
	}

	@Override
	public void connectUser(String username) throws DatabaseException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void decGuestCount() throws DatabaseException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void disconnectUser(String username) throws DatabaseException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public Vector<String> getConnectedRegisteredUsers()
			throws DatabaseException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getGuestCount() throws DatabaseException {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void incGuestCount() throws DatabaseException {
		// TODO Auto-generated method stub
		
	}
}
